using System;
using System.Collections.Generic;
using System.Text;
using Forecast.Database;

namespace Forecast.Business
{
    public class AcceptOffer
    {
        #region Variables

        private Player _buyer;
        private Player _seller;
        private Holding _buyerHolding;
        private Holding _sellerHolding;
        private Offer _offer;
        private Offer _subOffer;
        private NewsItem _newsItemSeller;
        private NewsItem _newsItemBuyer;
        private Prediction _prediction;
        private int _buyerHiddenHolding;

        #endregion
        #region Interface

        //TODO: This method name is very confusing
        //Probably buy and sell should be refactored and generalized
        public void Buy(Player buyer, Player seller, Holding buyerHolding, Holding sellerHolding, Prediction prediction, Offer offer, int numPredictions, int buyerHiddenHolding)
        {
            if (buyer == null)
                throw new ArgumentNullException("buyer");
            if (seller == null)
                throw new ArgumentNullException("seller");
            if (buyerHolding == null)
                throw new ArgumentNullException("buyerHolding");
            if (sellerHolding == null)
                throw new ArgumentNullException("sellerHolding");
            if (prediction == null)
                throw new ArgumentNullException("prediction");
            if (offer == null)
                throw new ArgumentNullException("offer");

            if (sellerHolding.NumPredictions < numPredictions)
                throw new ArgumentException(String.Format("{0} does not have {1} {2} for sale.", seller.UserName, numPredictions, prediction.Ticker));
            if (! offer.IsActive)
                throw new ArgumentException("The offer is not active any longer.");
            if (offer.EndDate < DateTime.Now)
                throw new ArgumentException(String.Format("The offers end date was {0}.", offer.EndDate));
            if (! prediction.IsActive)
                throw new ArgumentException(String.Format("The predicition {0} is no longer active.", prediction.Ticker));
            if (prediction.IsClosed)
                throw new ArgumentException(String.Format("The prediction {0} has closed.", prediction.Ticker));
            if (prediction.EndDate < DateTime.Now)
                throw new ArgumentException(String.Format("The prediction {0} end date has passed ({1}).", prediction.Ticker, prediction.EndDate));
        
            if (buyer.Id != buyerHolding.PlayerId)
                throw new ArgumentException(String.Format("{0} does not own the buyer holding.", buyer.UserName));
            if (seller.Id != sellerHolding.PlayerId)
                throw new ArgumentException(String.Format("{0} does not own the seller holding.", seller.UserName));
            if (buyerHolding.PredictionId != prediction.Id)
                throw new ArgumentException("Buyer holding is of the wrong prediction.");
            if (sellerHolding.PredictionId != prediction.Id)
                throw new ArgumentException("Seller holding is of the wrong prediction.");
            if (buyer.Id == seller.Id)
                throw new ArgumentException("Buyer is the same as seller. Can't buy from yourself.");
            if (offer.PredictionId != prediction.Id)
                throw new ArgumentException("Prediction does not match offer prediction.");
            if (buyer.Id != offer.PlayerId)
                throw new ArgumentException(String.Format("{0} has not made this offer.", buyer.UserName));
            if (numPredictions == 0)
                throw new ArgumentException("Must at least buy one prediction.");

            _buyer = buyer;
            _buyerHolding = buyerHolding;
            _sellerHolding = sellerHolding;
            _offer = offer;
            _seller = seller;
            _prediction = prediction;
            _buyerHiddenHolding = buyerHiddenHolding;
            PerformBuy(numPredictions);
        }

        //TODO: This method name is very confusing
        //Probably buy and sell should be refactored and generalized
        public void Sell(Player buyer, Player seller, Holding buyerHolding, Prediction prediction, Offer offer, int numPredictions, int buyerHiddenHolding)
        {
            if (buyer == null)
                throw new ArgumentNullException("buyer");
            if (seller == null)
                throw new ArgumentNullException("seller");
            if (buyerHolding == null)
                throw new ArgumentNullException("buyerHolding");
            if (prediction == null)
                throw new ArgumentNullException("prediction");
            if (offer == null)
                throw new ArgumentNullException("offer");

            if (buyer.Cash < (numPredictions * offer.Price))
                throw new ArgumentException(numPredictions.ToString() + " costs " +
                                            (numPredictions*offer.Price).ToString() + ". " + buyer.UserName +
                                            " only has " + buyer.Cash + ".");
            if (! offer.IsActive)
                throw new ArgumentException("The offer is not active any longer.");
            if (offer.EndDate < DateTime.Now)
                throw new ArgumentException("The offers end date was " + offer.EndDate.ToString() + ".");
            if (! prediction.IsActive)
                throw new ArgumentException("The predicition " + prediction.Ticker + " is no longer active.");
            if (prediction.IsClosed)
                throw new ArgumentException("The prediction " + prediction.Ticker + " has closed.");
            if (prediction.EndDate < DateTime.Now)
                throw new ArgumentException("The prediction " + prediction.Ticker + " end date has passed (" + prediction.EndDate.ToString() + ").");
            if (buyerHolding.PredictionId != prediction.Id)
                throw new ArgumentException("Buyer holding is of the wrong prediction.");
            if (offer.PredictionId != prediction.Id)
                throw new ArgumentException("Prediction does not match offer prediction.");
            if (seller.Id != offer.PlayerId)
                throw new ArgumentException(seller.UserName + " has not made this offer.");
            if (numPredictions == 0)
                throw new ArgumentException("Must at least buy one prediction.");

            _buyer = buyer;
            _buyerHolding = buyerHolding;
            _offer = offer;
            _seller = seller;
            _prediction = prediction;
            _buyerHiddenHolding = buyerHiddenHolding;
            PerformSell(numPredictions);
        }

        #endregion
        #region Private methods

        private void PerformBuy(int numPredictions)
        {
            UpdatePlayersForBuy(numPredictions);
            UpdateHoldingsForBuy(numPredictions);
            CreateNewsItems(numPredictions);
            UpdateOffer(numPredictions, _seller.Id);
            UpdatePrediction();
        }

        private void PerformSell(int numPredictions)
        {
            UpdatePlayersForSale(numPredictions);
            UpdateHoldingsForSale(numPredictions);
            CreateNewsItems(numPredictions);
            UpdateOffer(numPredictions, _buyer.Id);
            UpdatePrediction();
        }

        private void UpdatePrediction()
        {
            int oldValue = _prediction.LastValue;
            int newValue = _offer.Price;
            int difference = newValue - oldValue;
            double percentage = (double)difference/(double)oldValue;
            _prediction.LastValue = newValue;
            _prediction.LastChange = (decimal)(percentage*100);
        }

        private void CreateNewsItems(int numPredictions)
        {
            int amount = (numPredictions * _offer.Price);

            _newsItemSeller = new NewsItem();
            _newsItemSeller.Title = String.Format("{0} traded.", _prediction.Ticker);
            _newsItemSeller.Message = String.Format("{0} sold {1} {2} {3} to {4} for {5}{6}.",
                _seller.UserName,
                numPredictions,
                _prediction.Ticker,
                (numPredictions == 1) ? "stock" : "stocks",
                _buyer.UserName,
                _offer.Price,
                (numPredictions == 1) ? "" : " each");
            _newsItemSeller.NewsDate = DateTime.Now;
            _newsItemSeller.PlayerId = _seller.Id;
            _newsItemSeller.ShouldBeShownInMainNews = false;

            _newsItemBuyer = new NewsItem();
            _newsItemBuyer.Title = String.Format("{0} traded.", _prediction.Ticker);
            int numStocks = _buyerHolding.NumPredictions + _buyerHiddenHolding;
            _newsItemBuyer.Message = String.Format("{0} bought {1} {2} {3} from {4} for {5}{6}. {0} now has {7} {8} {9}.",
                _buyer.UserName,
                numPredictions,
                _prediction.Ticker,
                (numPredictions == 1) ? "stock" : "stocks",                
                _seller.UserName,
                _offer.Price,
                (numPredictions == 1) ? "" : " each",
                numStocks,
                _prediction.Ticker,
                (numPredictions == 1) ? "stock" : "stocks");
            _newsItemBuyer.NewsDate = DateTime.Now;
            _newsItemBuyer.PlayerId = _buyer.Id;
            _newsItemBuyer.PredictionId = _prediction.Id;
            _newsItemBuyer.ShouldBeShownInMainNews = false;
        }

        private void UpdateHoldingsForSale(int numPredictions)
        {
            _buyerHolding.NumPredictions += numPredictions;
        }

        private void UpdateHoldingsForBuy(int numPredictions)
        {
            _buyerHolding.NumPredictions += numPredictions;
            _sellerHolding.NumPredictions -= numPredictions;
        }

        private void UpdatePlayersForSale(int numPredictions)
        {
            int amount = (numPredictions*_offer.Price);
            _buyer.Cash -= amount;
            _seller.Cash += amount;
        }

        private void UpdatePlayersForBuy(int numPredictions)
        {
            int amount = (numPredictions * _offer.Price);
            _seller.Cash += amount;
        }

        private void UpdateOffer(int numPredictions, int acceptorId)
        {
            _offer.AcceptorId = acceptorId;
            _offer.IsAccepted = true;
            _offer.IsActive = false;
            _offer.EndDate = DateTime.Now;
            if (numPredictions < _offer.NumPredictions)
            {
                int remaindingPredictions = _offer.NumPredictions - numPredictions;
                _offer.NumPredictions = numPredictions;
                _subOffer = new Offer();
                _subOffer.EndDate = _offer.EndDate;
                _subOffer.IsAccepted = false;
                _subOffer.IsActive = true;
                _subOffer.IsBuy = _offer.IsBuy;
                _subOffer.NumPredictions = remaindingPredictions;
                _subOffer.PlayerId = _offer.PlayerId;
                _subOffer.PredictionId = _offer.PredictionId;
                _subOffer.Price = _offer.Price;
            }
        }

        #endregion
        #region Properties

        public Player AcceptOfferBuyer
        {
            get { return _buyer; }   
        }

        public Holding AcceptOfferBuyerHolding
        {
            get { return _buyerHolding; }
        }

        public Holding AcceptOfferSellerHolding
        {
            get { return _sellerHolding; }
        }

        public Offer AcceptOfferOffer
        {
            get { return _offer; }
        }

        public Offer AcceptOfferSubOffer
        {
            get { return _subOffer; }   
        }

        public Player AcceptOfferSeller
        {
            get { return _seller; }
        }

        public NewsItem AcceptOfferNewsItemBuyer
        {
            get { return _newsItemBuyer; }
        }

        public NewsItem AcceptOfferNewsItemSeller
        {
            get { return _newsItemSeller; }
        }

        public Prediction AcceptOfferPrediction
        {
            get { return _prediction; }
        }

        #endregion
    }
}
